pub use super::_entities::addresses::{self, ActiveModel, Entity, Model};
pub(crate) use crate::models::_entities::addresses::CreateAddressParams;
use loco_rs::prelude::*;
use serde::Deserialize;
use uuid::Uuid;
use crate::models::_entities::addresses::UpdateAddressParams;

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "Customer ID must not be empty."))]
    pub customer_id: String,
    #[validate(length(min = 1, message = "Name must not be empty."))]
    pub name: String,
    #[validate(length(min = 1, message = "Country ID must not be empty."))]
    pub country_id: String,
    #[validate(length(min = 1, message = "State ID must not be empty."))]
    pub state_id: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            customer_id: self.customer_id.as_ref().to_owned(),
            name: self.name.as_ref().to_owned(),
            country_id: self.country_id.as_ref().to_owned(),
            state_id: self.state_id.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds an address by the provided id
    ///
    /// # Errors
    ///
    /// When could not find address or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let address = addresses::Entity::find()
            .filter(
                query::condition()
                    .eq(addresses::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        address.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds addresses by the provided customer_id
    ///
    /// # Errors
    ///
    /// When could not find addresses or DB query error
    pub async fn find_by_customer_id(
        db: &DatabaseConnection,
        customer_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let addresses = addresses::Entity::find()
            .filter(
                model::query::condition()
                    .eq(addresses::Column::CustomerId, customer_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(addresses)
    }

    /// Asynchronously creates an address and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the address into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateAddressParams) -> ModelResult<Self> {
        let txn = db.begin().await?;

        let address = ActiveModel {
            customer_id: ActiveValue::set(params.customer_id.to_string()),
            guest_id: ActiveValue::set(params.guest_id.clone().unwrap_or_default()),
            name: ActiveValue::set(params.name.to_string()),
            email: ActiveValue::set(params.email.clone()),
            phone: ActiveValue::set(params.phone.clone().unwrap_or_default()),
            country_id: ActiveValue::set(params.country_id.to_string()),
            state_id: ActiveValue::set(params.state_id.to_string()),
            state: ActiveValue::set(params.state.to_string()),
            city_id: ActiveValue::set(params.city_id.clone()),
            city: ActiveValue::set(params.city.to_string()),
            zipcode: ActiveValue::set(params.zipcode.to_string()),
            address_1: ActiveValue::set(params.address_1.to_string()),
            address_2: ActiveValue::set(params.address_2.to_string()),
            default: ActiveValue::set(params.default),
            ..Default::default()
        }
        .insert(&txn)
        .await?;

        txn.commit().await?;

        Ok(address)
    }
}

impl ActiveModel {
    /// Updates the address's default status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_default_status(
         self,
        db: &DatabaseConnection,
        default: bool,
    ) -> ModelResult<Model> {
        let mut am: ActiveModel = self.into();
        am.default = ActiveValue::set(default);
        Ok(am.update(db).await?)
    }

    pub async fn update_address(
        self,
        db: &DatabaseConnection,
        params: UpdateAddressParams,
    ) -> ModelResult<Model> {
        let mut am: ActiveModel = self.into();
        if params.name.is_some() {
            am.name = ActiveValue::set(params.name.unwrap());
        }

        Ok(am.update(db).await?)
    }
}
